/*
 * Unidata Platform Community Edition
 * Copyright (c) 2013-2020, UNIDATA LLC, All rights reserved.
 * This file is part of the Unidata Platform Community Edition software.
 *
 * Unidata Platform Community Edition is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Unidata Platform Community Edition is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */
package org.unidata.mdm.dq.core.type.io;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;

import javax.annotation.Nonnull;

import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.unidata.mdm.core.type.data.DataRecord;
import org.unidata.mdm.system.type.namespace.NameSpace;
import org.unidata.mdm.system.util.NameSpaceUtils;

/**
 * @author Mikhail Mikhailov on Mar 11, 2021
 * Abstract records IO base.
 */
public abstract class AbstractInputOutput {
    /**
     * The input.
     */
    protected final Map<String, List<DataRecord>> records;
    /**
     * Additional records identity container - record and its ID.
     */
    protected final Map<DataRecord, String> identity;
    /**
     * Constructor.
     * @param input the input
     */
    protected AbstractInputOutput(Map<String, List<Pair<String, DataRecord>>> input) {
        super();
        if (MapUtils.isEmpty(input)) {
            records = null;
            identity = null;
        } else {

            records = new HashMap<>(input.size());
            identity = new IdentityHashMap<>();

            for (Entry<String, List<Pair<String, DataRecord>>> nss : input.entrySet()) {

                List<DataRecord> part = new ArrayList<>();
                for (int i = 0; i < nss.getValue().size(); i++) {

                    Pair<String, DataRecord> kv = nss.getValue().get(i);
                    part.add(kv.getValue());
                    identity.put(kv.getValue(), kv.getKey());
                }

                records.put(nss.getKey(), part);
            }
        }
    }
    /**
     * Gets input as map
     * @return map
     */
    @Nonnull
    protected Map<String, List<DataRecord>> getRecords() {
        return Objects.nonNull(records) ? records : Collections.emptyMap();
    }
    /**
     * Returns true if this input is empty.
     * @return true if this input is empty
     */
    public boolean isEmpty() {
        return MapUtils.isEmpty(records) || records.values().stream().allMatch(Collection::isEmpty);
    }
    /**
     * Gets all records and their IDs for default namespace (no namespace and no type name).
     * @return map of records keyed by ID
     */
    @Nonnull
    public Map<String, DataRecord> getAsIdentity() {
        return getAsIdentity(NameSpace.NAMESPACE_SEPARATOR);
    }
    /**
     * Gets all records and their IDs for the given namespace and type name.
     * @param ns the namespace
     * @param typeName the type name
     * @return map of records keyed by ID
     */
    @Nonnull
    public Map<String, DataRecord> getAsIdentity(NameSpace ns, String typeName) {
        final String key = NameSpaceUtils.join(ns, typeName);
        return getAsIdentity(key);
    }
    /**
     * Gets all records and their IDs for the given namespace and type name.
     * @param ns the namespace
     * @param typeName the type name
     * @return map of records keyed by ID
     */
    @Nonnull
    public Map<String, DataRecord> getAsIdentity(String ns, String typeName) {
        final String key = NameSpaceUtils.join(ns, typeName);
        return getAsIdentity(key);
    }
    /**
     * Gets all records and their IDs for the given _JOINED_ key (':' for global, no-named collection).
     * @param key the key
     * @return map of records keyed by ID
     */
    @Nonnull
    public Map<String, DataRecord> getAsIdentity(String key) {
        List<DataRecord> content = getRecords().get(key);
        return Objects.isNull(content) ? Collections.emptyMap() : content.stream()
                .collect(Collectors.toMap(identity::get, Function.identity()));
    }
    /**
     * Gets all records for default namespace (no namespace and no type name).
     * @return list of records
     */
    @Nonnull
    public List<DataRecord> getAsRecords() {
        return getAsRecords(NameSpace.NAMESPACE_SEPARATOR);
    }
    /**
     * Gets all records for the given namespace and type name.
     * @param ns the namespace
     * @param typeName the type name
     * @return list of records
     */
    @Nonnull
    public List<DataRecord> getAsRecords(NameSpace ns, String typeName) {
        final String key = NameSpaceUtils.join(ns, typeName);
        return getAsRecords(key);
    }
    /**
     * Gets all records for the given namespace and type name.
     * @param ns the namespace
     * @param typeName the type name
     * @return list of records
     */
    @Nonnull
    public List<DataRecord> getAsRecords(String ns, String typeName) {
        final String key = NameSpaceUtils.join(ns, typeName);
        return getAsRecords(key);
    }
    /**
     * Gets all records for the given _JOINED_ key (':' for global, no-named collection).
     * @param key the key
     * @return list of records
     */
    @Nonnull
    public List<DataRecord> getAsRecords(String key) {
        List<DataRecord> content = getRecords().get(key);
        return Objects.isNull(content) ? Collections.emptyList() : content;
    }
}
